package com.share51.novel.service.impl;

import com.google.common.collect.Lists;
import com.share51.novel.common.StatusConstant;
import com.share51.novel.common.ViewConstant;
import com.share51.novel.common.enums.NovelSiteEnums;
import com.share51.novel.common.parse.NovelParse;
import com.share51.novel.common.thread.DefaultThreadFactory;
import com.share51.novel.common.utils.HtmlUtil;
import com.share51.novel.common.utils.ZipUtil;
import com.share51.novel.config.CrawlSite;
import com.share51.novel.config.NovelConfig;
import com.share51.novel.entity.Chapter;
import com.share51.novel.entity.Content;
import com.share51.novel.entity.Crawl;
import com.share51.novel.entity.Novel;
import com.share51.novel.init.ApplicationCached;
import com.share51.novel.service.CrawlService;
import com.share51.novel.service.HttpClientService;
import com.share51.novel.service.NovelService;
import com.share51.novel.service.TaskService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.*;
import java.util.stream.Collectors;

/**
 * 定时任务业务处理
 *
 * @author gb
 */
@Slf4j
@Service("taskService")
public class TaskServiceImpl implements TaskService {

    @Resource
    private HttpClientService httpClientService;

    @Resource
    private NovelService novelService;

    @Resource
    private CrawlService crawlService;

    @Resource
    private NovelConfig novelConfig;

    private Random random=new Random();

    @Override
    public void syncNovelChapter() throws InterruptedException {
        // 获取小说列表
        List<Novel> novelList = novelService.getAllNovelBySeriesStatus(StatusConstant.NovelStatus.NOVEL_SERIES);
        Map<String, CrawlSite> crawlSite = novelConfig.getCrawlSite();
        for (Novel novel:novelList){
            asyncSyncNovelChapter(novel,crawlSite);
            randomSleep();
        }
    }

    @Async
    public void asyncSyncNovelChapter(Novel novel,Map<String, CrawlSite> crawlSite){
        CrawlSite site = crawlSite.get(NovelSiteEnums.of(novel.getSite()).getEnCode());
        String hostName= site.getUrl();
        String url = hostName+novel.getUrl();
        try {
            String html= httpClientService.doGet(url);
            List<Chapter> chapterList=NovelParse
                    .parseChapter(html,
                            NovelSiteEnums.of(novel.getSite()),
                            novel.getOrderIndex(),
                            site.getUrl());
            novelService.saveChapter(chapterList,novel.getId(),hostName);
            if (chapterList.isEmpty()&&novelIsEnd(novel)){
                // 修改 novel 状态
                novel.setSeriesStatus(StatusConstant.NovelStatus.NOVEL_SERIES_WAIT);
                novelService.editNovelSeriesStatus(novel);
            }else {
                novel.setSeriesStatus(StatusConstant.NovelStatus.NOVEL_SERIES);
                novelService.editNovelSeriesStatus(novel);
                novel.setStatus(StatusConstant.STATUS_OK);
                // 将未发布状态小说修改为正常
                novelService.editNovelStatusByStatus(novel,StatusConstant.NovelStatus.NOVEL_WAIT_PUBLISH);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * 判断是否完结(7天未更新 认为完结)
     * @param novel
     * @return
     */
    private boolean novelIsEnd(Novel novel){
        if (novel.getUpdateTime()==null){
            novel.setUpdateTime(new Date());
        }
        long maxEndTime= 1000L*60*60*24*7;
        return (novel.getUpdateTime().getTime()-System.currentTimeMillis()>maxEndTime);
    }

    private void randomSleep() throws InterruptedException {
        int minSleepTime=3000,maxSleepTime=5000;
        int time = random.nextInt(maxSleepTime);
        if (time<minSleepTime){
            time+=minSleepTime;
        }
        Thread.sleep(time);
    }

    @Override
    public void crawlChapter() throws IOException, InterruptedException {
        Map<String, CrawlSite> crawlSite = novelConfig.getCrawlSite();
        List<Chapter> chapterList = novelService
                .getAllChapterByStatus(StatusConstant.NovelChapterStatus.WAIT_CRAWL);
        chapterList
                .stream()
                .map(Chapter::getNovelId)
                .distinct()
                .forEach(novelId->{
                    novelService.updateNovelStatusAndNotDel(novelId,StatusConstant.STATUS_OK);
                });
        for (Chapter chapter:chapterList) {
            asyncCrawlChapter(crawlSite,chapter);
            randomSleep();
        }
    }

    @Async
    public void asyncCrawlChapter(Map<String, CrawlSite> crawlSite,Chapter chapter){
        try {
            String url=crawlSite.get(NovelSiteEnums
                    .of(chapter.getSite()).getEnCode())
                    .getUrl()+chapter.getUrl();
            String html=NovelParse
                    .parseDetail(httpClientService.doGet(url),
                            chapter.getSite());
            // 写数据库
            Content content=new Content();
            content.setText(html);
            novelService.saveContent(content);
            // 修改章节数据
            chapter.setContentId(content.getId());
            chapter.setStatus(StatusConstant.STATUS_OK);
            novelService.updateChapter(chapter);
            writeTxt(chapter,html);
        } catch (IOException e) {
            log.error("爬取章节详情内容出现异常",e);
        }
    }

    /**
     * 写成 txt 文件
     */
    private void writeTxt(Chapter chapter,String html) throws IOException{
        if (novelConfig.isEnableWriteTxt()) {
            int id = chapter.getNovelId();
            String filePath = novelConfig.getSaveFilePath() + ViewConstant.NOVEL_SAVE_URL + id + ".zip";
            String unzipFile = ZipUtil.unZip(filePath, true);
            List<String> list = Lists.newArrayList();
            list.add(chapter.getName());
            list.add(HtmlUtil.htmlToText(html));
            FileUtils.writeLines(new File(unzipFile), list, "\r\n", true);
            ZipUtil.zip(unzipFile, true);
        }
    }

    /**
     * 默认获取章节的最后几条记录
     */
    private static final int CHAPTER_LIMIT = 50;

    @Override
    public void initBloomFilter() throws Exception {
        if (ApplicationCached.SERVER_IS_INIT){
            return;
        }
        ThreadPoolExecutor executorService = new ThreadPoolExecutor(10,
                20,0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>(),new DefaultThreadFactory("chapter"));
        try {
            ApplicationCached.SERVER_IS_INIT=true;

            // 获取所有的小说列表
            List<Crawl> allCrawlUrlAndSite = crawlService.getAllCrawlUrlAndSiteAndId();
            List<String> result=Lists.newArrayList();
            allCrawlUrlAndSite.forEach(crawl -> {
                Future<List<String>> submit = executorService.submit(() -> {
                    // 根据编号获取前多少条记录
                    return novelService.getChapterByLimit(crawl.getNovelId(), CHAPTER_LIMIT);
                });
                try {
                    List<String> list = submit.get();
                    result.addAll(list
                            .stream()
                            .map(str->(crawl.getSite()+":"+str))
                            .collect(Collectors.toList()));
                } catch (Exception e) {
                    log.error("初始化布隆过滤器失败",e);
                }
            });
            ApplicationCached.initBloomFilter(allCrawlUrlAndSite.stream()
                    .map(crawl -> (crawl.getSite()+":"+crawl.getUrl()))
                    .collect(Collectors.toList()),result);
        } finally {
            ApplicationCached.SERVER_IS_INIT=false;
            if (executorService.isTerminating()) {
                executorService.shutdown();
            }
        }
    }
}
